package com.oazon.moco.lan.core.udp;

import java.util.concurrent.atomic.AtomicBoolean;

import android.content.Context;

import com.oazon.common.Assert;
import com.oazon.common.Logger;
import com.oazon.common.Settings;
import com.oazon.common.TaskHelper;
import com.oazon.common.TaskHelper.RunnableWithName;
import com.oazon.common.util.StringUtils;
import com.oazon.moco.lan.core.udp.DiscoveryReceiver.DiscoveryPacketListener;
import com.oazon.moco.link.core.LinkSDK;

public class LanDiscoveryServer implements DiscoveryPacketListener {
    private static final String TAG = "Discovery.Lan";
    public static final int DISCOVERY_LISTEN_PORT = 1900;
    private static final String DISCOVERY_VER_1_0 = "1.0";
    
    private DiscoverySender mSender;
    private DiscoveryReceiver mReceiver;
    private final Object mLock = new Object();

    private static Context mContext;
    private AtomicBoolean mRunning = new AtomicBoolean(false);

    public LanDiscoveryServer(Context ctx) {
        mContext = ctx;
        mSender = new DiscoverySender(ctx);
        mReceiver = new DiscoveryReceiver(ctx);
    }

    public void start() {
        Logger.d(TAG, "start peer discovery!");
        if (!mRunning.compareAndSet(false, true))
            return;

        try {
            mReceiver.addListener(this);
            mSender.start();
            mReceiver.start(DISCOVERY_LISTEN_PORT);
            
            TaskHelper.execZForSDK(new RunnableWithName("TS.Lan.online") {
                @Override
                public void execute() {
                    broadcastOnlineMessage();
                }
            });
        } catch (Exception e) {
            Logger.w(TAG, "start peer discovery failed!", e);
            stop();
        }
    }

    public void stop() {
        Logger.d(TAG, "stop peer discovery!");
        if (!mRunning.compareAndSet(true, false))
            return;
        
        synchronized (mLock) {
            try {
                mLock.notifyAll();
            } catch(Exception e) {
                Logger.w(TAG, "notifyAll: " + e.getMessage());
            }
        }
        mReceiver.removeListener(this);
        mReceiver.stop();
        mSender.stop();
        
        TaskHelper.execZForSDK(new RunnableWithName("TS.Lan.DiscoveryStop") {
            @Override
            public void execute() {
                // TODO: broad cast the MP offline
            }
        });
    }
    
    private void broadcastOnlineMessage() {
    		String onlineMsg = buildRspMsg(DISCOVERY_VER_1_0);
    		
    		long waitTime = 500;
    		long maxInterval = 10000;
    	    
    	    while(waitTime < maxInterval)
    	    {
    	    		broadcastMessage(onlineMsg);
    	    		synchronized (mLock) {
	    	    		try {
	    	    		    mLock.wait(waitTime);
	    	    		} catch(Exception e) {
	    	    			Logger.w(TAG, "broadcast online msg failed " + e.getMessage());
	    	    			break;
	    	    		}
    	    		}
    	    		
    	        if(waitTime <= 500)
    	            waitTime = 1000;
    	        else if (waitTime <= 1000)
    	            waitTime = 3000;
    	        else if (waitTime <= 3000)
    	            waitTime = 7000;
    	        else if (waitTime <= 7000)
    	            waitTime = maxInterval;
    	    }
    }

    private void broadcastMessage(String presence) {
        Assert.notNEWS(presence);

        try {
            Logger.d(TAG, "broadcastMessage...");
            mSender.broadcastPacket(presence, DISCOVERY_LISTEN_PORT + 1);
        } catch (Exception e) {
            Logger.w(TAG, "broad message:" + presence, e);
        }
    }

    private void sendMessage(String address, int port, String message) {
        Assert.notNEWS(address);
        Assert.isTrue(port > 2014);
        Assert.notNEWS(message);

        try {
            mSender.sendPacket(address, port, message);
        } catch (Exception e) {
            Logger.w(TAG, "send message:" + message.toString(), e);
        }
    }

    @Override
    public void onPacket(String fromIp, String packet) {
        Assert.notNEWS(fromIp);
        Assert.notNEWS(packet);
        
    		String ST = "ST:";
    		String MAN = "MAN:";
    		
        try {
        		// filter some packets those other devices broadcast 
        		if (packet.length() < 50)
        			return;
        		
        		String[] split = packet.split("\n");
        		
        		String notify = null;
        		String st = null;
        		String man = null;
        		
        		for (int i = 0; i < split.length; i++) {
        			String line = split[i];
        			if(i == 0) {
        				notify = line.trim();
        			} else if(line.toUpperCase().startsWith(ST)) {
        				st = line.substring(ST.length()).trim();
        			} else if(line.toUpperCase().startsWith(MAN)) {
        				man = line.substring(MAN.length()).trim();
        			} 	
        		}
        		
            String rspMsg = null;        		
        		
            // is it a M-SEARCH ?
        		if(notify != null && 
        			notify.toUpperCase().startsWith("M-SEARCH") &&
        			st != null && 
        			st.toLowerCase().startsWith("urn:schemas-upnp-org:device:o2player") &&
        			man != null && 
        			man.toLowerCase().startsWith("ssdp: discover")) {
        			rspMsg =  buildRspMsg(DISCOVERY_VER_1_0);
        			Logger.d(TAG, "RspMsg:" + rspMsg);
        		}
        		
            if (rspMsg == null)
                return;

            broadcastMessage(rspMsg);
        } catch (Exception e) {
            Logger.w(TAG, "onPacket: " + packet + "with Error:" + e.getMessage());
        }
    }

    private static String buildRspMsg(String msgVer) {
        Assert.notNEWS(msgVer);

        String mpName = null;
        mpName = Settings.getInstance(mContext).get("nsd.serv.name");
        if (mpName == null || mpName.length() < 1)
            mpName = "O2Player";
        Assert.notNEWS(mpName);
        	String deviceId = LinkSDK.UUID;
        	Assert.notNEWS(deviceId);
        	
        String str =null;
        if (StringUtils.isEquals(msgVer,DISCOVERY_VER_1_0)){
	        str = "HTTP/1.1 200 OK\r\n" +
	        		"HOST: 239.255.255.250:1900\r\n" +
	        		"CACHE-CONTROL: max-age = 1800\r\n" +
	        		"DEVICENAME:" + mpName + "\r\n" +
	        		"NT: uuid:" + deviceId + "\r\n" +
	        		"NTS: ssdp:alive/r/n" +
	        		"SERVER: UPnP/1.0 O2/Player1\r\n" +
	        		"USN: uuid:" + deviceId + "\r\n" +
	        		"ST: urn:schemas-upnp-org:device:O2Player:1\r\n\r\n";
        }
        
        Assert.notNEWS(str);
        return str;        
    }
}